#include "util/stdc.h"
#include "sip9/message.h"
namespace sip{

void encode_method(const Method &method, string &output)
{
    if (method == METHOD_INVITE)
        output += "INVITE";
    else if (method == METHOD_ACK)
        output += "ACK";
    else if (method == METHOD_BYE)
        output += "BYE";
    else if (method == METHOD_CANCEL)
        output += "CANCEL";
    else if (method == METHOD_UPDATE)
        output += "UPDATE";
    else if (method == METHOD_PRACK)
        output += "PRACK";
    else if (method == METHOD_MESSAGE)
        output += "MESSAGE";
    else if (method == METHOD_INFO)
        output += "INFO";
    else if (method == METHOD_REGISTER)
        output += "REGISTER";
    else if (method == METHOD_OPTIONS)
        output += "OPTIONS";
    else if (method == METHOD_SUBSCRIBE)
        output += "SUBSCRIBE";
    else if (method == METHOD_NOTIFY)
        output += "NOTIFY";
    else if (method == METHOD_REFER)
        output += "REFER";
    else if (method == METHOD_PUBLISH)
        output += "PUBLISH";
}

void encode_scheme(const Scheme &scheme, string &output)
{
    if (scheme == SCHEME_SIP)
        output += "sip";
    else if (scheme == SCHEME_SIPS)
        output += "sips";
}

void encode_uri_params(const std::map<string, string> &params, string &output)
{
    for (auto &param : params)
    {
        output += ";";
        output += param.first;

        if (!param.second.empty())
        {
            output += "=";
            output += param.second;
        }
    }
}

void encode_uri_headers(const std::map<string, string> &headers, string &output)
{
    if (!headers.empty())
    {
        output += "?";

        auto iter = headers.begin();

        while (true)
        {
            output += iter->first;
            output += "=";
            output += iter->second;

            if (++iter != headers.end())
                break;

            output += "&";
        }
    }
}

void encode_host(const Host &host, string &output)
{
    if (host.type == Host::HOST_NAME || host.type == Host::IN4_ADDR)
    {
        output += host.value;
    }
    else
    {
        assert(host.type == Host::IN6_ADDR);
        output += "[";
        output += host.value;
        output += "]";
    }
}

// UriSpec = Scheme ":" [Userinfo] Host [":" Port] *(";" UriParam) ["?" UriHeader *("&" UriHeader)]
// Scheme = "sip" / "sips"
// UserInfo = User [":" Pswd] "@"
// UriParam = ParamName ["=" ParamValue]
// UriHeader = HeaderName "=" HeaderValue
void encode_uri_spec(const UriSpec &uri, string &output)
{
    encode_scheme(uri.scheme, output);
    output += ":";

    if (!uri.user.empty())
    {
        output += uri.user;

        if (!uri.passwd.empty())
        {
            output += ":";
            output += uri.passwd;
        }

        output += "@";
    }

    encode_host(uri.host, output);

    if (uri.port > (u32)0)
    {
        output += ":";
        output += std::to_string(uri.port);
    }

    encode_uri_params(uri.params, output);
    encode_uri_headers(uri.headers, output);
}

void encode_param_value(const ParamValue &str, string &output)
{
    if (str.type == ParamValue::TOKEN)
    {
        output += str.value;
    }
    else
    {
        assert(str.type == ParamValue::QUOTED);
        output += "\"";
        output += str.value;
        output += "\"";
    }
}

void encode_params(const std::map<string, ParamValue> &params, string &output)
{
    for (auto &param : params)
    {
        output += ";";
        output += param.first;

        if (!param.second.empty())
        {
            output += "=";
            encode_param_value(param.second, output);
        }
    }
}

// CSeq = "CSeq" HCOLON 1*DIGIT LWS Method CRLF
void encode_header_cseq(const HeaderCseq &cseq, string &output)
{
    output += "CSeq: ";
    output += std::to_string(cseq.seq_num);
    output += " ";
    encode_method(cseq.method, output);
    output += "\r\n";
}

// CallId  = ("Call-ID"/"i") HCOLON word ["@" word] CRLF
void encode_header_call_id(const string &call_id, string &output)
{
    output += "Call-ID: ";
    output += call_id;
    output += "\r\n";
}

// From = ("From"/"f") HCOLON [DispName] LANGLE UriSpec RANGLE *(SEMI FromParam) CRLF
// DispName = QuoteStr / *(TokenStr LWS)
// FromParam = ParmName [EQUAL ParmValue]
void encode_header_from(const HeaderFrom &from, string &output)
{
    output += "From: ";

    if (!from.display.empty())
	{
		output += "\"";
		output += from.display;
		output += "\"";
	}

    output += "<";
    encode_uri_spec(from.uri, output);
    output += ">";

    encode_params(from.params, output);
    output += "\r\n";
}

// To = ("To"/"t") HCOLON [DispName] LANGLE UriSpec RANGLE *(SEMI ToParam) CRLF
// ToParam = ParmName [EQUAL ParmValue]
void encode_header_to(const HeaderTo &to, string &output)
{
    output += "To: ";

    if (!to.display.empty())
	{
		output += "\"";
		output += to.display;
		output += "\"";
	}

    output += "<";
    encode_uri_spec(to.uri, output);
    output += ">";

    encode_params(to.params, output);
    output += "\r\n";
}

// Via = ("Via"/"v") HCOLON ViaItem *(COMMA ViaItem) CRLF
// ViaItem = "SIP" SLASH "2.0" SLASH Proto LWS Host [COLON Port] *(SEMI ViaParm)
void encode_header_via(const HeaderVia &via, string &output)
{
    output += "Via: ";
    output += "SIP/2.0/";
    output += via.proto;
    output += " ";
	encode_host(via.host, output);

    if (via.port > (u32)0)
    {
        output += ":";
        output += std::to_string(via.port);
    }

    encode_params(via.params, output);
    output += "\r\n";
}

// Contact = ("Contact"/"m") HCOLON (STAR / ContactItem *(COMMA ContactItem)) CRLF
// ContactItem = [DispName] LANGLE UriSpec RANGLE *(SEMI ContactParam)
void encode_header_contact(const HeaderContact &contact, string &output)
{
    output += "Contact: ";

    if (!contact.display.empty())
    {
        output += "\"";
        output += contact.display;
        output += "\"";
    }

    output += "<";
    encode_uri_spec(contact.uri, output);
    output += ">";

    encode_params(contact.params, output);
    output += "\r\n";
}

// Route = "Route" HCOLON RouteItem *(COMMA RouteItem) CRLF
// RouteItem = [DispName] LANGLE UriSpec RANGLE *(SEMI Param)
void encode_header_route(const HeaderRoute &route, string &output)
{
    output += "Route: ";

	if (!route.display.empty())
	{
		output += "\"";
		output += route.display;
		output += "\"";
	}

    output += "<";
    encode_uri_spec(route.uri, output);
    output += ">";

    encode_params(route.params, output);
    output += "\r\n";
}

// RecordRoute = "Record-Route" HCOLON RouteItem *(COMMA RouteItem) CRLF
// RouteItem = [DispName] LANGLE UriSpec RANGLE *(SEMI Param)
void encode_header_record_route(const HeaderRoute &route, string &output)
{
    output += "Record-Route: ";

	if (!route.display.empty())
	{
		output += "\"";
		output += route.display;
		output += "\"";
	}

    output += "<";
    encode_uri_spec(route.uri, output);
    output += ">";

    encode_params(route.params, output);
    output += "\r\n";
}

// Extension = ExtName HCOLON ExtValue CRLF
void encode_header_ext(const HeaderExt &ext, string &output)
{
    //RFC3261: implementations should avoid spaces between the field name
    //  and the colon and use a single space (SP) between the colon and the field-value.
    output += ext.name;
    output += ": ";
    output += ext.value;
    output += "\r\n";
}

// Headers = ... / Extension
void encode_headers(const Headers &headers, string &output)
{
    assert(headers.cseq.is_owned());
    assert(headers.call_id.is_owned());
    assert(headers.from.is_owned());
    assert(headers.to.is_owned());
    assert(!headers.vias.empty());

    encode_header_cseq(headers.cseq.get_refer(), output);
    encode_header_call_id(headers.call_id.get_refer(), output);
    encode_header_from(headers.from.get_refer(), output);
    encode_header_to(headers.to.get_refer(), output);

    for (auto &header_via : headers.vias)
        encode_header_via(header_via, output);

    for (auto &header_route : headers.routes)
        encode_header_route(header_route, output);

    for (auto &header_rec_route : headers.record_routes)
        encode_header_record_route(header_rec_route, output);

    for (auto &header_contact : headers.contacts)
        encode_header_contact(header_contact, output);

    for (auto &header_ext : headers.exts)
        encode_header_ext(header_ext, output);
}

// Request = RequestLine Headers CRLF [Body]
// RequestLine = Method SP UriSpec SP Version CRLF
void encode_request(const Request &req, string &output)
{
    encode_method(req.method, output);
    output += " ";
    encode_uri_spec(req.req_uri, output);
    output += " SIP/2.0\r\n";

    encode_headers(req.headers, output);
    output += "\r\n";
    output += req.msg_body;
}

// Response = StatusLine Headers CRLF [Body]
// StatusLine = Version SP StatusCode SP ReasonPhrase CRLF
void encode_response(const Response &rsp, string &output)
{
    //RFC3261: The SIP-Version string is case-insensitive,
    //  but implementations MUST send upper-case.
    output += "SIP/2.0 ";
    output += std::to_string(rsp.status_code);
    output += " ";
    output += rsp.reason;
    output += "\r\n";

    encode_headers(rsp.headers, output);
    output += "\r\n";
    output += rsp.msg_body;
}

// Message = *(CRCF) (Request / Response)
int encode_message(const Message &msg, string &output)
{
    output.reserve(4096);

    if (msg.type == Message::REQUEST)
    {
        encode_request(msg.req, output);
    }
    else
    {
        assert(msg.type == Message::RESPONSE);
        encode_response(msg.rsp, output);
    }

    return 0;
}

int Message::to_str(string &output)
{
    return encode_message(*this, output);
}

}
